
#include <stdio.h>
#include <string.h>
#include <memory.h>

#include "cfg.h"

#define MAX_STD_MODE	(0x16)

#define EDID_LENGTH		(128)
#define ID_VERSION		(0x12)
#define	ID_STD_TIMINGS	(0x26)
#define ID_EXTENSION_FLAG	(126)

#define ID_EXTENSION_BASE	(128)
#define ID_EXTENSION_HEADER	(0x00)

struct version_info {
    char version;
    char revision;
};

struct timing_info {
    unsigned int h_res;
    unsigned int v_res;
    unsigned int frequency;
};

struct hdmi_mode_info {
	int std_timing_num;
	struct timing_info timing[MAX_STD_MODE];
};

#define _SUPPORT_MASK	(0x00000001<<0)
#define _NATIVE_MASK	(0x00000001<<1)

#define HDMI_NULL		(-1)
#define HDMI_720p		(0)
#define HDMI_720i		(1)

#define VIDEO_DES_INIT(mode, _h_res, _v_res, _rate) \
	{                        						\
		.name  = #mode,       						\
		.tag   = mode,        						\
		.h_res = _h_res,							\
		.v_res = _v_res,							\
		.frequency = _rate,							\
		.attr  = 0,           						\
	}

struct hdmi_video_descriptors {
	const char *name;
	int tag;
	unsigned int h_res;
	unsigned int v_res;
	unsigned int frequency;
	int attr;
};

#if 1
static struct hdmi_video_descriptors _hdmi_vds[] = {
    [0]  = VIDEO_DES_INIT(DISP_TV_MOD_720P_60HZ,  1280, 720,  60),
    [1]  = VIDEO_DES_INIT(DISP_TV_MOD_720P_50HZ,  1280, 720,  50),
    [2]  = VIDEO_DES_INIT(DISP_TV_MOD_1080P_60HZ, 1920, 1080, 60),
    [3]  = VIDEO_DES_INIT(DISP_TV_MOD_1080P_50HZ, 1920, 1080, 50),
    [4]  = VIDEO_DES_INIT(DISP_TV_MOD_1080P_30HZ, 1920, 1080, 30),
    [5]  = VIDEO_DES_INIT(DISP_TV_MOD_1080P_25HZ, 1920, 1080, 25),
    [6]  = VIDEO_DES_INIT(DISP_TV_MOD_1080P_24HZ, 1920, 1080, 24),
    [7]  = VIDEO_DES_INIT(DISP_TV_MOD_1080I_60HZ, 1920, 1080, 60),
    [8]  = VIDEO_DES_INIT(DISP_TV_MOD_1080I_50HZ, 1920, 1080, 50),
    [9]  = VIDEO_DES_INIT(DISP_TV_MOD_576P,       720,  576,  50),
    [10] = VIDEO_DES_INIT(DISP_TV_MOD_480P,       720,  480,  60),
    [11] = VIDEO_DES_INIT(DISP_TV_MOD_576I,       720,  576,  50),
    [12] = VIDEO_DES_INIT(DISP_TV_MOD_480I,       720,  480,  60),
    [13] = VIDEO_DES_INIT(DISP_TV_MOD_END,        0,    0,    0 ),
};
#else
static struct hdmi_video_descriptors _hdmi_vds[] = {
	[0] = VIDEO_DES_INIT(HDMI_720p),
	[1] = VIDEO_DES_INIT(HDMI_720i),
	[2] = VIDEO_DES_INIT(HDMI_NULL),
};
#endif

static struct hdmi_mode_info _hdmi_mode_info;


static const unsigned char edid_header0[] = { 0x00, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0x00
};
static int edid_check_header0(const unsigned char *header)
{
    int i;

    for (i = 0; i < 8; i++) {
        if (header[i] != edid_header0[i]) {
            printf("edid_check_header0 failed \n");
            return -1;
        }
    }
    return 0;
}

static int edid_checksum(const unsigned char *edid)
{
	return 0;
}

static const struct version_info edid_version[] = {
    {
        .version = 0x1,
        .revision = 0x3,
    },
};
static int edid_check_version(const unsigned char *version)
{
    int i;

    for(i = 0; i < sizeof(edid_version) / sizeof(struct version_info); i++) {
        if(edid_version[i].version == version[0] &&
            edid_version[i].revision == version[1]) {
            return 0;
        }
    }
    printf("edid_check_version failed\n");
    return -1;
}

int check_edid_block0(const unsigned char *edid, int const length)
{
    if((EDID_LENGTH <= length) &&
        !edid_checksum(edid) &&
        !edid_check_header0(edid) &&
        !edid_check_version(edid + ID_VERSION)) {
        return 0;
    }

    printf("check_edid_block0 failed\n");
    return -1;
}

int check_extension_block(const unsigned char *edid, int const length)
{
    if(0 == edid[ID_EXTENSION_FLAG] ||
      length < EDID_LENGTH + EDID_LENGTH ||
      2 != edid[ID_EXTENSION_BASE + ID_EXTENSION_HEADER] ||
      3 != edid[ID_EXTENSION_BASE + ID_EXTENSION_HEADER + 1] || //support version 3
      0 != edid_checksum(edid + ID_EXTENSION_BASE)) {
        return 0;
    }
    printf("Extension_block exist.\n");
    return 1;
}

static int cal_v_res(int x_res, int ratio)
{
    switch(ratio) {
    case 0: // 00=1:1 prior to v1.3
        return x_res * 10 / 16;
    case 1:
        return x_res * 3 / 4;
    case 2:
        return x_res * 4 / 5;
    case 3:
        return x_res * 9 / 16;
    default:
        return 0;
    }
}

static int get_std_mode(const unsigned char *buf, struct timing_info *info,
    int const max_num)
{
    int num = 0;
    int i = 0;
    int ratio = 0;
    struct timing_info mode;

    for(i = 0; i < 8 && max_num > num; i++, buf += 2) {
        if(0x01 != buf[0]) {
            mode.h_res = buf[0] * 8 + 248;
            ratio = (buf[1] >> 6) & 0x03;
            mode.v_res = cal_v_res(mode.h_res, ratio);
            mode.frequency = (buf[1] & 0x3F) + 60;
            memcpy((void *)info, (void *)&mode, sizeof(struct timing_info));
            info++;
            num++;
        }
    }

    return num;
}

static int get_hdmi_output_mode_info(const unsigned char *edid, struct hdmi_mode_info *info)
{
	const unsigned char *std_timing = edid + ID_STD_TIMINGS;

	info->std_timing_num = get_std_mode(std_timing, info->timing, MAX_STD_MODE);
	return 0;
}

static int initial_vds_by_stander_mode(const struct hdmi_mode_info *info,
	struct hdmi_video_descriptors *vds)
{
	int i = 0;
	const struct timing_info *timing;
	struct hdmi_video_descriptors *pvds;

	for (i=0; i<info->std_timing_num; i++) {
		pvds = vds;
		timing = &info->timing[i];
		while(pvds->tag != DISP_TV_MOD_END) {
			if (timing->h_res == pvds->h_res &&
				timing->v_res == pvds->v_res &&
				timing->frequency == pvds->frequency) {
				pvds->attr |= _SUPPORT_MASK;
				break; 
			}
			pvds++;
		}
	}

	return 0;
}

static struct hdmi_video_descriptors *get_vds_by_type(int type,
				struct hdmi_video_descriptors *vds)
{
	while(vds->tag != DISP_TV_MOD_END) {
		if (vds->tag == type) {
			return vds;
		}
		vds++;
	}

	return 0;
}

static int initial_vds_by_cea_code(int code, struct hdmi_video_descriptors *vds)
{
	unsigned int native = code & 0x80;
	unsigned int type = code & 0x7f;
	struct hdmi_video_descriptors *p = NULL;

	switch (type) {
	case CEA_MODE_720p_60Hz:
		p = get_vds_by_type(DISP_TV_MOD_END, vds);
		if (p) {
			p->attr |= _SUPPORT_MASK;
			p->attr |= native?_NATIVE_MASK:0;
		}
		break;
	default:
		fprintf(stderr, "xxxx\n");
	}
}

static void dump_std_timing(const struct hdmi_mode_info *info)
{
	int i;
	char dump[512] = {0};

	for (i=0; i<info->std_timing_num; i++) {
		sprintf(dump, "stander timing %d: %dx%d@%dHz\n",
			i, info->timing[i].h_res, info->timing[i].v_res, info->timing[i].frequency);
		fprintf(stdout, "%s", dump);
	}
}

static void dump_hdmi_video_descriptors(const struct hdmi_video_descriptors *vds)
{
	fprintf(stderr, "TV support input mode: [native] [support] name\n");
	while (vds->tag!=HDMI_NULL) {
		fprintf(stderr, "   [%c] [%c] %s\n",
			(vds->attr & _SUPPORT_MASK)?'*':' ',
			(vds->attr & _NATIVE_MASK)?'*':' ',
			vds->name);
		vds++;
	}
}

extern const unsigned char edid_buff[];
int main(int argc, char **argv)
{
	int ret = check_edid_block0(edid_buff, 256);
	int extended_block = 0;

	if (ret!=0) {
		fprintf(stderr, "edid check failed\n");
		return -1;
	}

	extended_block = check_extension_block(edid_buff, 256);
	if (extended_block)
		fprintf(stderr, "edid has extended block\n");

	get_hdmi_output_mode_info(edid_buff, &_hdmi_mode_info);
	initial_vds_by_stander_mode(&_hdmi_mode_info, _hdmi_vds);
	dump_std_timing(&_hdmi_mode_info);

//	initial_vds_by_cea_code_();	
	dump_hdmi_video_descriptors((const struct hdmi_video_descriptors *)&_hdmi_vds);
	return 0;
}
